using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Pathfinding;
using UnityEngine.EventSystems;

[HelpURL("http://arongranberg.com/astar/docs/class_turn_based_manager.php")]
public class TurnBasedManager : MonoBehaviour {
	//List<TurnBasedAI> agents = new List<TurnBasedAI>();
	TurnBasedAI selected;

	public float movementSpeed;
	public GameObject nodePrefab;
	public LayerMask layerMask;

	List<GameObject> possibleMoves = new List<GameObject>();
	EventSystem eventSystem;

	public State state = State.SelectUnit;

	public enum State {
		SelectUnit,
		SelectTarget,
		Move
	}

	void Awake () {
		eventSystem = FindObjectOfType<EventSystem>();
	}

	// Use this for initialization
	void Start () {
		//agents = new List<TurnBasedAI> (FindObjectsOfType<TurnBasedAI> ());
	}

	void Update () {
		var ray = Camera.main.ScreenPointToRay(Input.mousePosition);

		// Ignore any input while the mouse is over a UI element
		if (eventSystem.IsPointerOverGameObject()) {
			return;
		}

		if (state == State.SelectTarget) {
			HandleButtonUnderRay(ray);
		}

		if (state == State.SelectUnit || state == State.SelectTarget) {
			if (Input.GetKeyDown(KeyCode.Mouse0)) {
				var unitUnderMouse = GetByRay<TurnBasedAI>(ray);

				if (unitUnderMouse != null) {
					Select(unitUnderMouse);
					DestroyPossibleMoves();
					GeneratePossibleMoves(selected);
					state = State.SelectTarget;
				}
			}
		}
	}

	// TODO: Move to separate class
	void HandleButtonUnderRay (Ray ray) {
		var button = GetByRay<Astar3DButton>(ray);

		if (button != null && Input.GetKeyDown(KeyCode.Mouse0)) {
			button.OnClick();

			DestroyPossibleMoves();
			state = State.Move;
			StartCoroutine(MoveToNode(selected, button.node));
		}
	}

	T GetByRay<T>(Ray ray) where T : class {
		RaycastHit hit;

		if (Physics.Raycast(ray, out hit, float.PositiveInfinity, layerMask)) {
			return hit.transform.GetComponentInParent<T>();
		}
		return null;
	}

	void Select (TurnBasedAI unit) {
		selected = unit;
	}

	IEnumerator MoveToNode (TurnBasedAI unit, GraphNode node) {
		var path = ABPath.Construct(unit.transform.position, (Vector3)node.position);

		path.traversalProvider = unit.traversalProvider;

		// Schedule the path for calculation
		AstarPath.StartPath(path);

		// Wait for the path calculation to complete
		yield return StartCoroutine(path.WaitForPath());

		if (path.error) {
			// Not obvious what to do here, but show the possible moves again
			// and let the player choose another target node
			// Likely a node was blocked between the possible moves being
			// generated and the player choosing which node to move to
			Debug.LogError("Path failed:\n" + path.errorLog);
			state = State.SelectTarget;
			GeneratePossibleMoves(selected);
			yield break;
		}

		// Set the target node so other scripts know which
		// node is the end point in the path
		unit.targetNode = path.path[path.path.Count - 1];

		yield return StartCoroutine(MoveAlongPath(unit, path, movementSpeed));

		unit.blocker.BlockAtCurrentPosition();

		// Select a new unit to move
		state = State.SelectUnit;
	}

	/** Interpolates the unit along the path */
	static IEnumerator MoveAlongPath (TurnBasedAI unit, ABPath path, float speed) {
		if (path.error || path.vectorPath.Count == 0)
			throw new System.ArgumentException("Cannot follow an empty path");

		// Very simple movement, just interpolate using a catmull rom spline
		float distanceAlongSegment = 0;
		for (int i = 0; i < path.vectorPath.Count - 1; i++) {
			var p0 = path.vectorPath[Mathf.Max(i-1, 0)];
			// Start of current segment
			var p1 = path.vectorPath[i];
			// End of current segment
			var p2 = path.vectorPath[i+1];
			var p3 = path.vectorPath[Mathf.Min(i+2, path.vectorPath.Count-1)];

			var segmentLength = Vector3.Distance(p1, p2);

			while (distanceAlongSegment < segmentLength) {
				var interpolatedPoint = AstarSplines.CatmullRom(p0, p1, p2, p3, distanceAlongSegment / segmentLength);
				unit.transform.position = interpolatedPoint;
				yield return null;
				distanceAlongSegment += Time.deltaTime * speed;
			}

			distanceAlongSegment -= segmentLength;
		}

		unit.transform.position = path.vectorPath[path.vectorPath.Count - 1];
	}

	void DestroyPossibleMoves () {
		foreach (var go in possibleMoves) {
			GameObject.Destroy(go);
		}
		possibleMoves.Clear();
	}

	void GeneratePossibleMoves (TurnBasedAI unit) {
		var path = ConstantPath.Construct(unit.transform.position, unit.movementPoints * 1000 + 1);

		path.traversalProvider = unit.traversalProvider;

		// Schedule the path for calculation
		AstarPath.StartPath(path);

		// Force the path request to complete immediately
		// This assumes the graph is small enough that
		// this will not cause any lag
		path.BlockUntilCalculated();

		foreach (var node in path.allNodes) {
			if (node != path.startNode) {
				// Create a new node prefab to indicate a node that can be reached
				// NOTE: If you are going to use this in a real game, you might want to
				// use an object pool to avoid instantiating new GameObjects all the time
				var go = GameObject.Instantiate(nodePrefab, (Vector3)node.position, Quaternion.identity) as GameObject;
				possibleMoves.Add(go);

				go.GetComponent<Astar3DButton>().node = node;
			}
		}
	}
}
